 <!DOCTYPE html>
 <html>
 <head>
 	<meta charset="utf-8">
 	<title>日志查询</title>
	 <link rel="icon" href="https://llootong.net/cloud/logo.png" type="image/png">

	<script src="js/vue.js"></script>
	 <!-- 接口调用-->
	 <script src="js/axios.min.js"></script>
	 <script src="js/clipboard.min.js"></script>
	 <!-- 引入组件库 -->
	 <script src="js/element-ui.js"></script>
	 <!-- 引入样式 -->
	 <link rel="stylesheet" href="js/element-ui.css">
	 <link rel="stylesheet" href="js/element-ui-display.css">

	 <!-- 引入组件库 -->
	 <script src="js/prism.js"></script>
	 <!-- 引入样式 -->
	 <link rel="stylesheet" href="js/prism.css">
 </head>
 <body>
	<div id="app">
		<div v-if="login">
			<el-form :inline="true" :model="from" class="demo-form-inline" ref="from" label-width="100px">
				<el-select placeholder="日志等级" style="width:8%" v-model="from.logLevel" clearable @change="onSubmit">
					<el-option value="" key="ALL">所有</el-option>
					<el-option value="INFO" key="INFO">INFO</el-option>
					<el-option value="ERROR" key="ERROR">ERROR</el-option>
					<el-option value="WARN" key="WARN">WARN</el-option>
					<el-option value="DEBUG" key="DEBUG">DEBUG</el-option>
				</el-select>
                <el-input style="width:8%" v-model="from.serverName" placeholder="服务器地址" clearable @change="onSubmit"></el-input>
                <el-input style="width:8%"  v-model="from.appName" placeholder="服务名称" clearable @change="onSubmit"></el-input>
                <el-input style="width:8%"  v-model="from.traceId" placeholder="追踪码" clearable @change="onSubmit"></el-input>
                <el-date-picker style="width:25%" :default-time="['00:00:00', '23:59:59']" v-model="dateTimeRange" type="datetimerange" value-format="yyyy-MM-dd HH:mm:ss" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :clearable="false"></el-date-picker>
				<el-input style="width:15%"  v-model="searchKey" placeholder="搜索条件" clearable></el-input>
				<el-form-item>
					<el-button  type="primary" @click="onSubmit">查询</el-button>
					<el-button  type="primary" @click="onClean">重置</el-button>
				</el-form-item>
			</el-form>

			<el-row style="white-space:nowrap; text-align: center;" :gutter="24">
				<el-col :xs="1" :sm="1" :md="1" :lg="1" :xl="1" class="hidden-md-and-down"><p>
					<el-button type="primary" icon="el-icon-search" size="mini" @click="toSql" circle></el-button>
					<el-button type="danger" icon="el-icon-delete" size="mini" @click="clearCheckBox" circle></el-button>
				</p></el-col>
				<el-col :xs="6" :sm="5" :md="4" :lg="3" :xl="3" >
					<p>日志时间</p>
				</el-col>
				<el-col 				:md="2" :lg="1" :xl="1" class="hidden-md-and-down"><p>日志等级</p></el-col>
				<el-col 				:md="4" :lg="2" :xl="2" class="hidden-md-and-down"><p>服务IP</p></el-col>
				<el-col :xs="4" :sm="3" :md="2" :lg="1" :xl="2"><p>服务名称</p></el-col>
				<el-col :xs="4" :sm="3" :md="4" :lg="2" :xl="1"><p>日志追踪</p></el-col>
			</el-row>
			<div class="infinite-list" v-infinite-scroll="load" style="overflow-y: auto;" :style="{height:(fullHeight-130)+'px'}" >
				<el-collapse>
				<template v-for="(item, i) in list" class="infinite-list-item">
					<el-collapse-item style="width:100%;" class="cx">
						<template slot="title" style="width:90%;">
							<!--:style="{background:'ERROR'==item.logLevel?'#fde2e2':i%2==1?'#eee':'#fff'}"-->
							<el-row class="contentRow"  style="white-space:nowrap;width:100%; text-align: center;" :style="{background:'ERROR'==item.logLevel?'#fde2e2':i%2==0?'#eee':'#fff'}">
								<el-col :xs="1" :sm="1" :md="1" :lg="1" :xl="1" class="hidden-md-and-down"><p><el-checkbox v-model="item.check"></el-checkbox></p></el-col>
								<el-col :xs="6" :sm="5" :md="4" :lg="3" :xl="3" ><p>{{item.time}}</p></el-col>
								<el-col 				:md="2" :lg="1" :xl="1" class="hidden-md-and-down"><p><el-link type="primary" @click="rzdj(item.logLevel)">{{item.logLevel}}</el-link></p></el-col>
								<el-col 				:md="4" :lg="2" :xl="2" class="hidden-md-and-down"><p><el-link type="primary" @click="serverName(item.serverName)">{{item.serverName}}</el-link></p></el-col>
								<el-col :xs="4" :sm="3" :md="2" :lg="1" :xl="2"><p style="overflow: hidden;text-overflow:ellipsis;white-space: nowrap;"><el-link type="primary" @click="appName(item.appName)">{{item.appName}}</el-link></p></el-col>
								<el-col :xs="4" :sm="3" :md="4" :lg="2" :xl="1"><p><el-link type="primary" @click="trace(item.traceId)">{{item.traceId}}</el-link></p></el-col>
								<p style="overflow: hidden;text-overflow:ellipsis;white-space: nowrap;text-align: left; padding-left: 2rem;" v-html="lightCode(item.content,300)"></p>
							</el-row>
						</template>
						<div style="padding:0 4rem; overflow-x:auto;overflow-y:hidden;">
							<p>服务器IP: {{item.serverName}}</p>
							<p>类: {{item.className}}</p>
							<p>线程: {{item.method}}</p>
							<p v-html="hightLightCode(item.content)"></p>
						</div>
					</el-collapse-item>
				</template>
				</el-collapse>
			</div>
			<input id="copyNode" type="hidden">
		</div>
		<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="250px" :show-close="false" :close-on-click-modal="false" :center="true">

		</el-dialog>
	</div>
	<script type="text/javascript">
		new Vue({
		  	el: '#app',
			data: function() {
				return {
					from:{
						logLevel:""
						,traceId:""
                        ,appName:""
					},
					url:window.location.protocol+"//"+window.location.host,
					dateTimeRange:[new Date(new Date(new Date().toLocaleDateString()).getTime()), new Date(new Date(new Date().toLocaleDateString()).getTime() + 24 * 60 * 60 * 1000 - 1)],
					searchKey:'',
					fullHeight: document.documentElement.clientHeight,
					list: [],
					sort:[{"microTime":"desc"}],
					size:50,
					page:0,
					total:0,
					lock:false,
					isIndeterminate: true,
					checkAll: false,
					copyData:'',
					drawer: false,
					tableData:[],
					esIndex:'plume_log_run_',
					dialogVisible: false,
					qrCode:'',
					login:false,
					openid:'',
				}
			},methods:{

				lightCode(code, limit){
				    if (!code){
				        return '';
                    }
				    code = code.replaceAll("<","&lt;")
					if (limit && code.length > limit){
						code = code.substring(0,limit);
					}
					if(this.searchKey){
						let re = new RegExp("(" + this.searchKey.replace(/\*/g,'') + ")", "gmi");
						code = code.replace(re, '<em>$1</em>');
					}
					return '<div style="word-break:break-all">'+code+"</div>";
				},
				hightLightCode(code){
                    if (!code){
                        return '';
                    }
                    code = code.replaceAll("<","&lt;")
					if(this.searchKey){
						let re = new RegExp("(" + this.searchKey.replace(/\*/g,'') + ")", "gmi");
						code = code.replace(re, '<em>$1</em>');
					}
					if(code.indexOf('java.')>-1){
						return Prism.highlight(code, Prism.languages.stackjava, 'stackjava').replace(/\n/g,'<br/>').replace(/&lt;/g,'<').replace(/&gt;/g,'>')
					} else {
						return code.replace(/\n/g,'<br/>');
					}
				},
				clearCheckBox() {
					//清空选择
					this.list.forEach(item => {
						item.check = false;
					})
				},
				trace(traceId){
					this.from.traceId = traceId;
					this.onSubmit();
				},
				rzdj(level){
					this.from.logLevel = level;
					this.onSubmit();
				},
                serverName(serverName){
				  this.from.serverName = serverName;
				  this.onSubmit();
                },
                appName(appName){
                    this.from.appName = appName;
                    this.onSubmit();
                },
				onSubmit(){
					this.page = 0;
					this.list = [];
					this.get();
				},
				get(){
					var a = 0;
					try {
						a = this.exec()
					} catch(err) {
						this.$message.error("错误:"+err);
					} finally {
						if(a != -1){
							this.page = this.page + this.size;
							this.lock = false;
						}
					}
				},
				exec(){
					if(this.lock || this.page > this.total){
						return -1;
					}
					this.lock = true;
					this.total=0;
					if (!this.dateTimeRange || !this.dateTimeRange.length){
						this.$message.error("请选择时间!");
						return 0;
					}
					let filters = [];
					for(let itemKey in this.from) {
						if(this.from[itemKey]){
							filters.push({
								"match_phrase_prefix":{
									[itemKey]:{
										"query":this.from[itemKey].replace(/,/g,' ')
									}
								}
							})
						}
					}
					if(this.searchKey){
						var key = this.searchKey.indexOf('"')==-1?'"'+this.searchKey+'"':this.searchKey;
						filters.push({
							"query_string":{
								"query": key,
								"default_field": "content"
							}
						})
					}

					//列出范围内的日期
					let dateList=[];
					//判断日期
					if(this.dateTimeRange.length>0 && this.dateTimeRange[0]!=''){
						let startDate=new Date(this.dateTimeRange[0]);
						let endDate=new Date(this.dateTimeRange[1]);
						filters.push({
							"range":{
								"dtTime":{
									"gte": Date.parse(startDate),
									"lt": Date.parse(endDate),
								}
							}
						});
						if(startDate){
							while(startDate<=endDate){
								dateList.push('plume_log_run_'+this.dateFormat('yyyyMMdd', startDate))
								startDate = new Date(startDate.setDate(startDate.getDate()+1));
							}
						}
					}

					let query = {"query":{"bool":{"must":filters}},"highlight": {"fields" : {"content" : {}}},"sort":this.sort};
					let url= this.url+"/serverlog/query?index="+(dateList.join(','))+"&size="+this.size+"&from="+this.page;
                    let $this = this;
					axios.post(url,query,{headers: {'openid': this.openid}}).then(function (response) {
						if(response.data.hits && response.data.hits.total){
							$this.total = response.data.hits.total.value;
							response.data.hits.hits.forEach(v=>{
								var it = v._source;
								it.id = v._id;
								it.time = $this.dateFormat('yyyy-MM-dd HH:mm:ss',new Date(it.dtTime));
								$this.list.push(it);
							})
						}
					}).catch(function (error) {
						console.log(error);
					});
				},
				onClean(){
					this.from = {logLevel:"",traceId:"",appName:""}
					this.page = 0;
					this.searchKey="";
					this.total = 0;
					this.dateTimeRange=[new Date(new Date(new Date().toLocaleDateString()).getTime()), new Date(new Date(new Date().toLocaleDateString()).getTime() + 24 * 60 * 60 * 1000 - 1)]
					this.list = [];
					this.get();
				},load () {
					this.get();
				},dateFormat(fmt, date) {
					let ret;
					const opt = {
						"y+": date.getFullYear().toString(),        // 年
						"M+": (date.getMonth() + 1).toString(),     // 月
						"d+": date.getDate().toString(),            // 日
						"H+": date.getHours().toString(),           // 时
						"m+": date.getMinutes().toString(),         // 分
						"s+": date.getSeconds().toString()          // 秒
						// 有其他格式化字符需求可以继续添加，必须转化成字符串
					};
					for (let k in opt) {
						ret = new RegExp("(" + k + ")").exec(fmt);
						if (ret) {
							fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
						};
					};
					return fmt;
				},left(str, i){
					//左截取

				},toSql(){
					var statementStr = null;
					var parametersStr = null;
					for (var i =0; i<this.list.length; i++){
						var item = this.list[i];
						if (item.check){
							if (parametersStr==null && item.content.indexOf("Parameters: ")!=-1){
								parametersStr = item.content.substring(item.content.lastIndexOf('Parameters: ')+12);
							} else if(statementStr==null && item.content.indexOf("Preparing: ")!=-1){
								statementStr = item.content.substring(item.content.lastIndexOf('Preparing: ')+11)
							}
						}
						if (statementStr!=null && parametersStr!=null){
							break;
						}
					}
					if (statementStr==null || parametersStr==null){
						this.$message.error("请选择sql和参数!");
						return;
					}
					var parametersStr = parametersStr.split(",");
					for(var i = 0; i < parametersStr.length; i++) {
						// 如果数据中带括号将使用其他逻辑
						tempStr = parametersStr[i].substring(0, parametersStr[i].indexOf("("));
						// 获取括号中内容
						typeStr = parametersStr[i].substring(parametersStr[i].indexOf("(")+1,parametersStr[i].indexOf(")"));
						// 如果为字符类型
						if (typeStr == "String" || typeStr == "Timestamp") {
							statementStr = statementStr.replace("?", "'"+tempStr.trim()+"'");
						}else{
							// 数值类型
							statementStr = statementStr.replace("?", tempStr.trim());
						}
					}
					//转sql语句
					this.copyData = statementStr;
					document.getElementById('copyNode').click()
				},getCookie(name) {
					var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
					if (arr = document.cookie.match(reg))
						return (arr[2]);
					else
						return null;
				},setCookie (c_name, value, expiredays) {
					var exdate = new Date();
					exdate.setDate(exdate.getDate() + expiredays);
					document.cookie = c_name + "=" + escape(value) + ((expiredays == null) ? "" : ";expires=" + exdate.toGMTString());
				},delCookie (name) {
                    var exp = new Date();
                    exp.setTime(exp.getTime() - 1);
                    var cval = this.getCookie(name);
                    if (cval != null)
                        document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
                },guid() {
					return new Date().getTime();
				},getQueryParam(key) {
                    var arr = location.search.substring(1).split('&')
                    for (var i = 0; i < arr.length; i++) {
                        var pair = arr[i].split('=')
                        if (pair[0] == key) {
                            return pair[1]
                        }
                    }
                    return undefined
                }

		    },mounted(){
                let $this = this;
                var uuid = this.getCookie('uuid');
                if(!uuid){
                    window.opener = null;
                    window.open('', '_self');
                    window.close();
                    return;
                }else{
                    $this.dialogVisible = false;
                    $this.login=true;
                    $this.openid = uuid;
                }

                const clipboard = new ClipboardJS('#copyNode', {
                    text: trigger => {
                        this.$notify({
                            title: '成功',
                            message: '代码已复制到剪切板，可粘贴。',
                            type: 'success'
                        })
                        return this.copyData;
                    }
                })
                clipboard.on('error', e => {
                    this.$message.error('代码复制失败')
                })
		  	}
		})

 	</script>
 	<style>
		body{
			overflow:scroll;
			overflow-x:hidden;
			overflow-y:hidden;
		}

		/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
		::-webkit-scrollbar {
			width: 5px;  /*滚动条宽度*/
			height: 10px;  /*滚动条高度*/
		}

		/*定义滚动条轨道 内阴影+圆角*/
		::-webkit-scrollbar-track {
			-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
			border-radius: 5px;  /*滚动条的背景区域的圆角*/
			background-color: #fff;/*滚动条的背景颜色*/
		}

		/*定义滑块 内阴影+圆角*/
		::-webkit-scrollbar-thumb {
			border-radius: 2px;  /*滚动条的圆角*/
			-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
			background-color: #ddd;  /*滚动条的背景颜色*/
		}

		.contentRow:hover{
			 background-color:#d9ecff!important
		 }
		.contentRow p{
			margin: 0;
		}
		.el-collapse-item__arrow{
			display: none;
		}
		.cx em {
			background: #ff0;
		}
	</style>
 </body>
 </html>
